/*
 * Copyright 2007-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package jsr203.sun.nio.fs;

import java.io.IOException;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import jsr203.nio.file.FileRef;
import jsr203.nio.file.ProviderMismatchException;
import jsr203.nio.file.attribute.Attribute;
import jsr203.nio.file.attribute.BasicFileAttributeView;
import jsr203.nio.file.attribute.BasicFileAttributes;
import jsr203.nio.file.attribute.FileOwnerAttributeView;
import jsr203.nio.file.attribute.PosixFileAttributeView;
import jsr203.nio.file.attribute.PosixFilePermission;
import jsr203.nio.file.attribute.UserPrincipal;
import jsr203.nio.file.attribute.UserPrincipalNotFoundException;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.chmod;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.chown;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.close;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.fchmod;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.futimes;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.getgrnam;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.getpwnam;
import static jsr203.sun.nio.fs.UnixNativeDispatcher.lchown;

class UnixFileAttributeView
    implements PosixFileAttributeView
{
    private final boolean needRuntimePermissionCheck;

    // encapsulates the object that we are bound too
    private volatile FileInfo fileInfo;

    private FileInfo getFileInfo() {
        FileInfo res = fileInfo;
        if (res == null)
            throw new IllegalStateException();
        return fileInfo;
    }

    private UnixFileAttributeView(boolean needRuntimePermissionCheck) {
        this.needRuntimePermissionCheck = needRuntimePermissionCheck;
    }

    static BasicFileAttributeView createBasicView() {
        final UnixFileAttributeView view = new UnixFileAttributeView(false);

        // SECURITY: must wrap to prevent casting to PosixFileAttributeView
        return new BasicFileAttributeView() {
            @Override
            public BasicFileAttributeView bind(FileRef obj) {
                view.bind(obj);
                return this;
            }
            @Override
            public BasicFileAttributeView bind(FileRef obj, boolean followLinks) {
                view.bind(obj, followLinks);
                return this;
            }
            @Override
            public BasicFileAttributes readAttributes() throws IOException {
                return view.readAttributes().asBasicFileAttributes();
            }
            @Override
            public BasicFileAttributeView setTimes(Long lastModifiedTime,
                                                   Long lastAccessTime,
                                                   Long createTime,
                                                   TimeUnit unit)
                throws IOException
            {
                view.setTimes(lastModifiedTime, lastAccessTime,
                              createTime, unit);
                return this;
            }
        };
    }

    static PosixFileAttributeView createPosixView() {
        return new UnixFileAttributeView(true);
    }

    static FileOwnerAttributeView createOwnerView() {
        final UnixFileAttributeView view = new UnixFileAttributeView(true);
        return new FileOwnerAttributeView() {
            @Override
            public FileOwnerAttributeView bind(FileRef obj) {
                view.bind(obj);
                return this;
            }
            @Override
            public FileOwnerAttributeView bind(FileRef obj, boolean followLinks) {
                view.bind(obj, followLinks);
                return this;
            }
            @Override
            public UserPrincipal getOwner() throws IOException {
                return view.readAttributes().owner();
            }
            @Override
            public FileOwnerAttributeView setOwner(UserPrincipal owner)
                throws IOException
            {
                view.setOwner(owner);
                return this;
            }
            @Override
            public UserPrincipal lookupPrincipalByName(String name)
                throws IOException
            {
                return view.lookupPrincipalByName(name);
            }
            @Override
            public UserPrincipal lookupPrincipalByGroupName(String group)
                throws IOException
            {
                return view.lookupPrincipalByGroupName(group);
            }
        };
    }

    private void checkAccess(UnixPath file,
                             boolean checkRead,
                             boolean checkWrite,
                             boolean checkRuntime)
    {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            if (checkRead)
                sm.checkRead(file.getPathForPermissionCheck());
            if (checkWrite)
                sm.checkWrite(file.getPathForPermissionCheck());
            if (checkRuntime)
                sm.checkPermission(new RuntimePermission("accessUserInformation"));
        }
    }

    @Override
    public PosixFileAttributeView bind(FileRef obj) {
        return bind(obj, true);
    }

    @Override
    public PosixFileAttributeView bind(FileRef obj, boolean followLinks) {
        if (obj != null && !(obj instanceof UnixPath))
            throw new ProviderMismatchException();
        fileInfo = (obj == null) ? null : new FileInfo(obj, followLinks);
        return this;
    }

    @Override
    public UnixFileAttributes readAttributes() throws IOException {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();

        checkAccess(file, true, false, needRuntimePermissionCheck);
        try {
             UnixFileAttributes attrs =
                 UnixFileAttributes.get(file.getPathForSysCalls(), info.followLinks());
             return attrs;
        } catch (UnixException x) {
            x.rethrowAsIOException(file);
            return null;    // keep compiler happy
        }
    }

    @Override
    public Attribute<Set<PosixFilePermission>> newPermissionsAttribute() {
        return new UnixFileModeAttribute();
    }

    @Override
    public PosixFileAttributeView setTimes(Long lastModifiedTime,
                                           Long lastAccessTime,
                                           Long createTime, // ignore
                                           TimeUnit unit)
        throws IOException
    {
        // null => don't change
        if (lastModifiedTime == null && lastAccessTime == null) {
            // no effect
            return this;
        }

        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        boolean followLinks = info.followLinks();
        checkAccess(file, false, true, false);

        int fd = file.openForAttributeAccess(followLinks);
        try {
            UnixFileAttributes attrs = null;

            // if not changing both attributes then need existing attributes
            if (lastModifiedTime == null || lastAccessTime == null) {
                try {
                    attrs = UnixFileAttributes.get(fd);
                } catch (UnixException x) {
                    x.rethrowAsIOException(file);
                }
            }

            // modified time = existing, now, or new value
            long modTime;
            if (lastModifiedTime == null) {
                modTime = attrs.lastModifiedTime();
            } else {
                if (lastModifiedTime >= 0L) {
                    modTime = TimeUnit.MILLISECONDS.convert(lastModifiedTime, unit);
                } else {
                    if (lastModifiedTime != -1L)
                        throw new IllegalArgumentException();
                    modTime = System.currentTimeMillis();
                }
            }

            // access time = existing, now, or new value
            long accTime;
            if (lastAccessTime == null) {
                accTime = attrs.lastAccessTime();
            } else {
                if (lastAccessTime >= 0L) {
                    accTime = TimeUnit.MILLISECONDS.convert(lastAccessTime, unit);
                } else {
                    if (lastAccessTime != -1L)
                        throw new IllegalArgumentException();
                    accTime = System.currentTimeMillis();
                }
            }

            try {
                futimes(fd, accTime, modTime);
            } catch (UnixException x) {
                x.rethrowAsIOException(file);
            }
        } finally {
            close(fd);
        }

        return this;
    }


    @Override
    public PosixFileAttributeView setPermissions(Set<PosixFilePermission> perms)
        throws IOException
    {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();
        checkAccess(file, false, true, false);

        int mode = UnixFileModeAttribute.toUnixMode(perms);

        try {
            if (info.followLinks()) {
                chmod(file.getPathForSysCalls(), mode);
            } else {
                int fd = file.openForAttributeAccess(false);
                try {
                    fchmod(fd, mode);
                } finally {
                    close(fd);
                }
            }
        } catch (UnixException x) {
            x.rethrowAsIOException(file);
        }

        return this;
    }

    private PosixFileAttributeView updateOwners(int uid, int gid)
        throws IOException
    {
        FileInfo info = getFileInfo();
        UnixPath file = (UnixPath)info.file();

        // permission check
        checkAccess(file, false, true, true);

        try {
            if (info.followLinks()) {
                chown(file.getPathForSysCalls(), uid, gid);
            } else {
                lchown(file.getPathForSysCalls(), uid, gid);
            }
        } catch (UnixException x) {
            x.rethrowAsIOException(file);
        }
        return this;
    }

    @Override
    public PosixFileAttributeView setOwner(UserPrincipal owner)
        throws IOException
    {
        if (owner.isGroup())
            throw new IllegalArgumentException("'owner' parameter can't be a group");
        if (!(owner instanceof UnixUserPrincipal))
            throw new ProviderMismatchException();
        int uid = ((UnixUserPrincipal)owner).uid();
        return updateOwners(uid, -1);
    }

    @Override
    public PosixFileAttributeView setGroup(UserPrincipal group)
        throws IOException
    {
        if (!group.isGroup())
            throw new IllegalArgumentException("'group' parameter must be group");
        if (!(group instanceof UnixUserPrincipal))
            throw new ProviderMismatchException();
        int gid = ((UnixUserPrincipal)group).gid();
        return updateOwners(-1, gid);
    }


    // package-private
    static int lookupPrincipalByName(String name, boolean isGroup)
        throws IOException
    {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("lookupUserInformation"));
        }
        int id = -1;
        try {
            id = (isGroup) ? getgrnam(name.getBytes()) : getpwnam(name.getBytes());
        } catch (UnixException x) {
            throw new IOException(name + ": " + x.errorString());
        }
        if (id == -1)
            throw new UserPrincipalNotFoundException(name);
        return id;

    }

    @Override
    public UserPrincipal lookupPrincipalByName(String name)
        throws IOException
    {
        int uid = lookupPrincipalByName(name, false);
        return UnixUserPrincipal.fromUid(uid, name);
    }

    @Override
    public UserPrincipal lookupPrincipalByGroupName(String group)
        throws IOException
    {
        int gid = lookupPrincipalByName(group, true);
        return UnixUserPrincipal.fromGid(gid, group);
    }
}
